package com.jt808.decode.utils;

import com.jt808.decode.constant.Jt808Constant;
import com.jt808.decode.model.Jt808Message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

/**
 * JT808 decoder
 * @date 20230302
 **/
@Slf4j
public class Jt808ProtocolDecoder {
    /**
     * Preliminary decoding of raw data, parsing into public classes
     * @param msg
     * @return
     */
    public static Jt808Message decode(ByteBuf msg){
        //message length for readable
        int msgLen = msg.readableBytes();
        //message header
        msg.readByte();
        //message id
        int msgId = msg.readUnsignedShort();
        //message body properties
        short msgBodyAttr = msg.readShort();
        //message body length
        int msgBodyLen = msgBodyAttr & 0b00000011_11111111;
        //is multi packet?
        boolean multiPacket = (msgBodyAttr & 0b00100000_00000000) > 0;
        //Version ID (version ID 0 refers to the version in 2011 and 1 refers to the version in 2019)
        int versionFlag = (msgBodyAttr & 0b01000000_00000000)>0?1:0;
        //the basic length of remove the message body
        int baseLen = Jt808Constant.MSG_BASE_LENGTH;
        if (versionFlag == 1) {
            baseLen = Jt808Constant.JT2019_MSG_BASE_LENGTH;
        }
        //The length of the following packet is determined by the length of the message body and whether it is subcontracted
        int ensureLen = multiPacket ? baseLen + msgBodyLen + 4 : baseLen + msgBodyLen;
        if (msgLen != ensureLen) {
            log.error("Wrong message length,Current message length:{},The correct length:{},Message:{}", msgLen, ensureLen, ByteBufUtil.hexDump(msg));
            return null;
        }
        //Data encryption method
        int encryptType = (msgBodyAttr & 0b00011100_00000000) >> 10;
        //Protocol version Number
        int protocolVersion = 0;
        //Terminal phone number array,JT808-2019 is 10 bytes
        byte[] deviceIdArr;
        if (versionFlag == 1) {
            protocolVersion = msg.readByte();
            deviceIdArr = new byte[10];
        } else {
            deviceIdArr = new byte[6];
        }
        msg.readBytes(deviceIdArr);
        //Terminal phone number (remove the 0 before)
        String deviceId = StringUtils.stripStart(ByteBufUtil.hexDump(deviceIdArr), "0");
        //Message serial number
        int msgFlowId = msg.readUnsignedShort();
        //Total number of message packets
        int packetTotalCount = 0;
        //packet number
        int packetOrder = 0;
        //multi packet?
        if (multiPacket) {
            packetTotalCount = msg.readShort();
            packetOrder = msg.readShort();
        }
        //message body array
        byte[] msgBodyArr = new byte[msgBodyLen];
        msg.readBytes(msgBodyArr);
        //check code
        int checkCode = msg.readUnsignedByte();
        //message tail
        msg.readByte();

        //Calculate and verify check codes
        ByteBuf checkSumBuf = msg.slice(1, msgLen - 3);
        int checkSumResult = CommonUtil.xor(checkSumBuf);
        if (checkSumResult != checkCode) {
            log.error("The check code fails.,Results of calculation:{},check code:{},message id:{},device id:{},message:{}", checkSumResult, checkCode, NumberUtil.formatMessageId(msgId), deviceId, ByteBufUtil.hexDump(msg));
            return null;
        }

        //Construct the Jt808 message and pass it to the next handler for processing
        Jt808Message jt808Msg = new Jt808Message();
        jt808Msg.setMsgId(msgId);
        jt808Msg.setEncryptType(encryptType);
        jt808Msg.setVersionFlag(versionFlag);
        jt808Msg.setMultiPacket(multiPacket);
        jt808Msg.setProtocolVersion(protocolVersion);
        jt808Msg.setDeviceId(deviceId);
        jt808Msg.setDeviceIdArr(deviceIdArr);
        jt808Msg.setMsgFlowId(msgFlowId);
        jt808Msg.setPacketTotalCount(packetTotalCount);
        jt808Msg.setPacketOrder(packetOrder);
        jt808Msg.setMsgBody(Unpooled.wrappedBuffer(msgBodyArr));
        return jt808Msg;
    }
}
